﻿using System;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Xml;
using System.Xml.Serialization;
using Dsl.Ped.Common.Extensions;
using Dsl.Ped.Editor.Helpers;
using Dsl.Ped.Framework.Common;

namespace Dsl.Ped.Editor.Services 
{
    /// <summary>
    /// Service class handles the lifetime of a <see cref="IProjective"/> object,
    /// its loading and saving to the file
    /// </summary>
    public class ProjectiveObjectService : IProjectiveObjectService
    {
        #region Fields

        /// <summary>
        /// <see cref="IAddInRegistrationService"/> used for the file handling
        /// </summary>
        private readonly IAddInRegistrationService addInRegistrationService;
        
        /// <summary>
        /// Currently opened file name
        /// </summary>
        private string currentFileName;

        /// <summary>
        /// Current projective type of the selected file
        /// </summary>
        private Type currentProjectiveType;

        /// <summary>
        /// Tha namespace which the object is associated with
        /// </summary>
        private string currentXmlNamespace;

        /// <summary>
        /// Current projective object which has been loaded from a XML file
        /// </summary>
        private IProjective currentProjectiveObject;

        #endregion

        #region Constructor
        
        /// <summary>
        /// Initializes a new instance of the <see cref="ProjectiveObjectService"/> class. 
        /// </summary>
        /// <param name="addInRegistrationService">Instance of <see cref="IAddInRegistrationService"/></param>
        public ProjectiveObjectService(IAddInRegistrationService addInRegistrationService)
        {
            this.addInRegistrationService = addInRegistrationService;
        }

        #endregion 
     
        #region Members

        /// <summary>
        /// Gets the type of the current <see cref="IProjective"/> object
        /// </summary>
        public Type CurrentProjectiveType
        {
            get
            {
                return this.currentProjectiveType;
            }
        }

        /// <summary>
        /// Gets the current <see cref="IProjective"/> object
        /// </summary>
        public IProjective CurrentProjectiveObject
        {
            get
            {
                return this.currentProjectiveObject;
            }
        }

        /// <summary>
        /// Gets the name of a file where the current <see cref="IProjective"/> object comes
        /// from
        /// </summary>
        public string CurrentFileName
        {
            get
            {
                return this.currentFileName;
            }
        }

        /// <summary>
        /// Gets the Xml Namespace of the file which was the <see cref="IProjective"/> object loaded
        /// from
        /// </summary>
        public string CurrentXmlNamespace
        {
            get
            {
                return this.currentXmlNamespace;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Load the <see cref="IProjective"/> object from the file of a given
        /// name 
        /// </summary>
        /// <param name="fileName">Name of the file which the <see cref="IProjective"/> object should be loaded from</param>
        /// <returns>True, if the file was loaded successfully</returns>
        public bool LoadFromFile(string fileName)
        {
            this.currentFileName = fileName;
            this.currentXmlNamespace = this.GetXmlNamespace(fileName);

            return this.LoadXmlFile2ProjectiveObject(fileName, addInRegistrationService);
        }

        /// <summary>
        /// Load the <see cref="IProjective"/> object from the file using the <see cref="XmlReader"/>
        /// </summary>
        /// <param name="fileName">Name of the file which the <see cref="IProjective"/> object should be loaded from</param>
        /// <param name="xmlReader"><see cref="XmlReader"/> used for the load of an object</param>
        /// <returns>True, if the file was loaded successufully</returns>
        public bool LoadFromFile(string fileName, XmlReader xmlReader)
        {
            this.currentFileName = fileName;
            this.currentXmlNamespace = this.GetXmlNamespace(fileName);

            return this.LoadXmlFile2ProjectiveObject(fileName, addInRegistrationService, xmlReader);
        }

        /// <summary>
        /// Reload the current <see cref="IProjective"/> from the current <see cref="CurrentFileName"/>
        /// </summary>
        /// <returns>True, if the <see cref="IProjective"/> object was reloaded successfully</returns>
        public bool ReloadFromFile()
        {
            if (string.IsNullOrEmpty(this.currentFileName))
            {
                return true;
            }

            this.LoadXmlFile2ProjectiveObject(this.currentFileName, this.addInRegistrationService);
            return true;
        }

        /// <summary>
        /// Store the <see cref="CurrentProjectiveObject"/> to the file
        /// </summary>
        /// <returns>True if the file was saved successfully, False otherwise</returns>
        public bool Store2File()
        {
            return this.StoreProjectiveObject2XmlFile(this.currentFileName);
        }

        /// <summary>
        /// Store the <see cref="CurrentProjectiveObject"/> to the file of a given name
        /// </summary>
        /// <param name="fileName">Name of the file which the <see cref="CurrentProjectiveObject"/> should be stored to</param>
        /// <returns>True, if the <see cref="CurrentProjectiveObject"/> was stored successfully</returns>
        public bool Store2File(string fileName)
        {
            return this.StoreProjectiveObject2XmlFile(fileName);
        }

        /// <summary>
        /// Create new <see cref="IProjective"/> object of a given <see cref="Type"/>
        /// </summary>
        /// <param name="projectiveType"><see cref="Type"/> of the <see cref="IProjective"/> object to be create</param>
        /// <returns>True, if the <see cref="IProjective"/> object was instantiated successfully</returns>
        public bool CreateNewProjectiveObject(Type projectiveType)
        {
            try
            {
                this.currentProjectiveObject = (IProjective)Activator.CreateInstance(projectiveType);
                this.currentProjectiveType = projectiveType;
                this.currentFileName = null;

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Create new <see cref="IProjective"/> object of a given <see cref="Type"/>
        /// </summary>
        /// <param name="projectiveType"><see cref="Type"/> of the <see cref="IProjective"/> object to be create</param>
        /// <param name="defaultObject">Use this object as a default</param>
        /// <returns>True, if the <see cref="IProjective"/> object was instantiated successfully</returns>
        public bool CreateNewProjectiveObject(Type projectiveType, object defaultObject)
        {
            try
            {
                if (defaultObject.GetType() == projectiveType)
                {
                    this.currentProjectiveObject = defaultObject as IProjective;
                    this.currentProjectiveType = projectiveType;
                    this.currentFileName = null;

                    return true;
                } 
                    
                return CreateNewProjectiveObject(projectiveType);
            } 
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Get the xml namespace of the given fileName
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private string GetXmlNamespace(string fileName)
        {
            var xmlNamespace = XmlHelper.GetXmlNamespace(fileName);
            return xmlNamespace;
        }

        /// <summary>
        /// Loads XML file to a <see cref="IProjective"/> object
        /// </summary>
        /// <param name="fileName">The file to be opened</param>
        /// <param name="addInRegService">The addIn Registration Service.</param>
        /// <returns>True, if the file was loaded successfully</returns>
        private bool LoadXmlFile2ProjectiveObject(string fileName, IAddInRegistrationService addInRegService, XmlReader xmlReader = null) {

            // The xml namespace of a file
            var xmlNamespace = XmlHelper.GetXmlNamespace(fileName);
            if (string.IsNullOrEmpty(xmlNamespace))
            {
                Debug.WriteLine("XmlNamespace: " + xmlNamespace);
                throw new Exception("XmlNamespace of the file " + fileName + " is missing");
            }

            this.currentProjectiveType = addInRegService.GetRegisteredType(xmlNamespace);
            if (this.currentProjectiveType == null)
            {
                Debug.WriteLine("File type not registered");
                throw new Exception("File type not registered");
            }
            
            var resourceDictionary = addInRegService.GetResourceDictionaryByType(currentProjectiveType);
            Application.Current.Resources.LoadResourceDictionary(resourceDictionary);

            object generatedObject;
            if (xmlReader == null)
            {
                var xmlFileContent = FileHelper.GetFileContent(fileName);
                if (xmlFileContent == null)
                {
                    Debug.WriteLine("File not found");
                    throw new FileNotFoundException("File not found", fileName);
                }

                generatedObject = XmlHelper.DeserializeXmlContent(currentProjectiveType, xmlNamespace, xmlFileContent);
            }
            else
            {
                generatedObject = XmlHelper.DeserializeXmlContent(currentProjectiveType, xmlNamespace, xmlReader);
            }
            
            this.currentProjectiveObject = (generatedObject is IProjective) ? (IProjective)generatedObject : null;

            return true;
        }

        /// <summary>
        /// Stores the <see cref="CurrentProjectiveObject"/> to the file of a given name
        /// </summary>
        /// <param name="fileName">Name of the file which </param>
        /// <returns>True, if the <see cref="CurrentProjectiveObject"/> was stored successfully</returns>
        private bool StoreProjectiveObject2XmlFile(string fileName)
        {
            try
            {
                var defaultNamespace = addInRegistrationService.GetNamespaceByType(CurrentProjectiveType);

                // Projective type serializer
                var serializer = new XmlSerializer(currentProjectiveType, defaultNamespace);
                var writer = new StringWriter();
                serializer.Serialize(writer, currentProjectiveObject);

                string str = writer.ToString();
                File.WriteAllText(fileName, str);
                return true;
            }
            catch (Exception exc)
            {
                Debug.Write("Exception while serialization: " + exc);
                return false;
            }
        }      

        #endregion
    }
}
